<?php
namespace Leaps;
use Leaps\Base\InvalidParamException;
use Leaps\Base\UnknownClassException;
use Leaps\Base\InvalidConfigException;
use Leaps\Log\Logger;

/*
 * 获取应用程序开始时间戳。
 */
defined ( 'LEAPS_BEGIN_TIME' ) or define ( 'LEAPS_BEGIN_TIME', microtime ( true ) );

/**
 * 定义框架所在目录
 */
defined ( 'LEAPS_PATH' ) or define ( 'LEAPS_PATH', __DIR__ );

/**
 * 定义调试模式。默认值为FALSE。
 */
defined ( 'LEAPS_DEBUG' ) or define ( 'LEAPS_DEBUG', false );

/**
 * 定义应用程序的运行环境.
 * 默认为 'prod', 表示生产环境
 * 你也可以在引导脚本中重新定义, 值可以是 'prod' (production), 'dev' (development), 'test',
 * 'staging', etc.
 */
defined ( 'LEAPS_ENV' ) or define ( 'LEAPS_ENV', 'prod' );

/**
 * 是否在生产环境中运行的应用程序
 */
defined ( 'LEAPS_ENV_PROD' ) or define ( 'LEAPS_ENV_PROD', LEAPS_ENV === 'prod' );

/**
 * 是否在开发环境中运行的应用程序
 */
defined ( 'LEAPS_ENV_DEV' ) or define ( 'LEAPS_ENV_DEV', LEAPS_ENV === 'dev' );

/**
 * 是否在测试环境中运行的应用程序
 */
defined ( 'LEAPS_ENV_TEST' ) or define ( 'LEAPS_ENV_TEST', LEAPS_ENV === 'test' );


/**
 * 定义错误处理是否启用
 */
defined ( 'LEAPS_ENABLE_ERROR_HANDLER' ) or define ( 'LEAPS_ENABLE_ERROR_HANDLER', true );

class BaseLeaps
{
	/**
	 * @var array 类地图
	 * The array keys are the class names (without leading backslashes), and the array values
	 * are the corresponding class file paths (or path aliases). This property mainly affects
	 * how [[autoload()]] works.
	 * @see autoload()
	 */
	public static $classMap = [];

	/**
	 * @var \Leaps\Console\Application|\Leaps\Web\Application the application instance
	 */
	public static $app;

	/**
	 * 注册的路径别名
	 * @var array 路径别名
	 * @see getAlias()
	 * @see setAlias()
	 */
	public static $aliases = ['@Leaps' => __DIR__];

	/**
	 * 初始属性值将被通过[[createObject]]应用到新创建的对象.
	 * 数组类名健和相应的数组值木有反斜杠,
	 * For example,
	 *
	 * ~~~
	 * [
	 *     'Bar' => [
	 *         'prop1' => 'value1',
	 *         'prop2' => 'value2',
	 *     ],
	 *     'mycompany\foo\Car' => [
	 *         'prop1' => 'value1',
	 *         'prop2' => 'value2',
	 *     ],
	 * ]
	 * ~~~
	 * @var array
	 * @see createObject()
	 */
	public static $objectConfig = [];



	/**
     * 创建应用程序实例
	 * @return \Leaps\Base\FrontController
	 */
	public static function createApplication($type = 'Web', $config = array()) {
		if (static::$app === null) {
			$_className = '\\Leaps\\' . $type . '\\FrontController';
			static::$app = new $_className($config);
		}
		return static::$app;
	}

	/**
	 * 获取框架版本
	 * @return string
	 */
	public static function getVersion()
	{
		return '2.0-dev';
	}

	/**
	 * 将路径别名转化为实际的路径。
	 *
	 * @param string $alias 要翻译的别名
	 * @param boolean $throwException 是否抛出异常,如果给定的别名是无效的。
	 * 如果这是错误和无效的别名,该方法将返回错误。
	 * @return string|boolean 相对路径别名,假如果根别名不是以前注册的。
	 * @throws InvalidParamException if the alias is invalid while $throwException is true.
	 * @see setAlias()
	 */
	public static function getAlias($alias, $throwException = true)
	{
		if (strncmp($alias, '@', 1)) {// 不是一个别名
			return $alias;
		}
		$pos = strpos($alias, '/');
		$root = $pos === false ? $alias : substr($alias, 0, $pos);
		if (isset(static::$aliases[$root])) {
			if (is_string(static::$aliases[$root])) {
				return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos);
			} else {
				foreach (static::$aliases[$root] as $name => $path) {
					if (strpos($alias . '/', $name . '/') === 0) {
						return $path . substr($alias, strlen($name));
					}
				}
			}
		}
		if ($throwException) {
			throw new InvalidParamException("Invalid path alias: $alias");
		} else {
			return false;
		}
	}

	/**
	 * Returns the root alias part of a given alias.
	 * A root alias is an alias that has been registered via [[setAlias()]] previously.
	 * If a given alias matches multiple root aliases, the longest one will be returned.
	 * @param string $alias the alias
	 * @return string|boolean the root alias, or false if no root alias is found
	 */
	public static function getRootAlias($alias)
	{
		$pos = strpos($alias, '/');
		$root = $pos === false ? $alias : substr($alias, 0, $pos);

		if (isset(static::$aliases[$root])) {
			if (is_string(static::$aliases[$root])) {
				return $root;
			} else {
				foreach (static::$aliases[$root] as $name => $path) {
					if (strpos($alias . '/', $name . '/') === 0) {
						return $name;
					}
				}
			}
		}
		return false;
	}

	/**
	 * 注册一个路径别名。
	 *
	 * @throws InvalidParamException if $path is an invalid alias.
	 * @see getAlias()
	 */
	public static function setAlias($alias, $path)
	{
		if (strncmp($alias, '@', 1)) {
			$alias = '@' . $alias;
		}
		$pos = strpos($alias, '/');
		$root = $pos === false ? $alias : substr($alias, 0, $pos);
		if ($path !== null) {
			$path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path);
			if (!isset(static::$aliases[$root])) {
				if ($pos === false) {
					static::$aliases[$root] = $path;
				} else {
					static::$aliases[$root] = [$alias => $path];
				}
			} elseif (is_string(static::$aliases[$root])) {
				if ($pos === false) {
					static::$aliases[$root] = $path;
				} else {
					static::$aliases[$root] = [
					$alias => $path,
					$root => static::$aliases[$root],
					];
				}
			} else {
				static::$aliases[$root][$alias] = $path;
				krsort(static::$aliases[$root]);
			}
		} elseif (isset(static::$aliases[$root])) {
			if (is_array(static::$aliases[$root])) {
				unset(static::$aliases[$root][$alias]);
			} elseif ($pos === false) {
				unset(static::$aliases[$root]);
			}
		}
	}

	/**
	 * 自动装载类加载程序。
	 * @param string $className the fully qualified class name without a leading backslash "\"
	 * @throws UnknownClassException if the class does not exist in the class file
	 */
	public static function autoload($className)
	{
		if (isset(static::$classMap[$className])) {
			$classFile = static::$classMap[$className];
			if ($classFile[0] === '@') {
				$classFile = static::getAlias($classFile);
			}
		} else {
			// follow PSR-0 to determine the class file
			if (($pos = strrpos($className, '\\')) !== false) {
				// namespaced class, e.g. Leaps\Base\Component
				$path = str_replace('\\', '/', substr($className, 0, $pos + 1))
				. str_replace('_', '/', substr($className, $pos + 1)) . '.php';
			} else {
				$path = str_replace('_', '/', $className) . '.php';
			}
			// 试着通过路径加载别名
			if (strpos($path, '/') === false) {
				return;
			} else {
				$classFile = static::getAlias('@' . $path, false);
				if ($classFile === false || !is_file($classFile)) {
					return;
				}
			}
		}
		include($classFile);
		if (LEAPS_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {
			throw new UnknownClassException("Unable to find '$className' in file: $classFile");
		}
	}

	/**
	 * 使用给定的配置创建一个新的对象。
	 *
	 * 配置可以是一个字符串或一个数组。
	 * 如果是字符串将被视为类名,如果是数组,必须包含'class'键指定对象类,数组的其他键值将用于初始化类的对应属性.
	 *
	 * Below are some usage examples:
	 *
	 * ~~~
	 * $object = \Leaps::createObject('app\components\GoogleMap');
	 * $object = \Leaps::createObject([
	 *     'class' => 'app\components\GoogleMap',
	 *     'apiKey' => 'xyz',
	 * ]);
	 * ~~~
	 *
	 * This method can be used to create any object as long as the object's constructor is
	 * defined like the following:
	 *
	 * ~~~
	 * public function __construct(..., $config = []) {
	 * }
	 * ~~~
	 *
	 * The method will pass the given configuration as the last parameter of the constructor,
	 * and any additional parameters to this method will be passed as the rest of the constructor parameters.
	 *
	 * @param string|array $config the configuration. It can be either a string representing the class name
	 * or an array representing the object configuration.
	 * @return mixed the created object
	 * @throws InvalidConfigException if the configuration is invalid.
	 */
	public static function createObject($config)
	{
		static $reflections = [];

		if (is_string($config)) {
			$class = $config;
			$config = [];
		} elseif (isset($config['class'])) {
			$class = $config['class'];
			unset($config['class']);
		} else {
			throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
		}

		$class = ltrim($class, '\\');
		if (isset(static::$objectConfig[$class])) {
			$config = array_merge(static::$objectConfig[$class], $config);
		}
		if (($n = func_num_args()) > 1) {
			/** @var \ReflectionClass $reflection */
			if (isset($reflections[$class])) {
				$reflection = $reflections[$class];
			} else {
				$reflection = $reflections[$class] = new \ReflectionClass($class);
			}
			$args = func_get_args();
			array_shift($args); // remove $config
			if (!empty($config)) {
				$args[] = $config;
			}
			return $reflection->newInstanceArgs($args);
		} else {
			return empty($config) ? new $class : new $class($config);
		}
	}

	/**
	 * 记录跟踪消息到日志
	 * 跟踪消息记录主要为开发目的.
	 * 一些代码的执行工作流程。
	 * @param string $message the message to be logged.
	 * @param string $category the category of the message.
	 */
	public static function trace($message, $category = 'application')
	{
		if (LEAPS_DEBUG) {
			static::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category);
		}
	}

	/**
	 * Logs an error message.
	 * An error message is typically logged when an unrecoverable error occurs
	 * during the execution of an application.
	 * @param string $message the message to be logged.
	 * @param string $category the category of the message.
	 */
	public static function error($message, $category = 'application')
	{
		static::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category);
	}

	/**
	 * Logs a warning message.
	 * A warning message is typically logged when an error occurs while the execution
	 * can still continue.
	 * @param string $message the message to be logged.
	 * @param string $category the category of the message.
	 */
	public static function warning($message, $category = 'application')
	{
		static::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category);
	}

	/**
	 * Logs an informative message.
	 * An informative message is typically logged by an application to keep record of
	 * something important (e.g. an administrator logs in).
	 * @param string $message the message to be logged.
	 * @param string $category the category of the message.
	 */
	public static function info($message, $category = 'application')
	{
		static::$app->getLog()->log($message, Logger::LEVEL_INFO, $category);
	}

	/**
	 * 标志着一个代码块的开始分析。
	 * This has to be matched with a call to [[endProfile]] with the same category name.
	 * The begin- and end- calls must also be properly nested. For example,
	 *
	 * ~~~
	 * \Leaps::beginProfile('block1');
	 * // some code to be profiled
	 *     \Leaps::beginProfile('block2');
	 *     // some other code to be profiled
	 *     \Leaps::endProfile('block2');
	 * \Leaps::endProfile('block1');
	 * ~~~
	 * @param string $token token for the code block
	 * @param string $category the category of this log message
	 * @see endProfile()
	 */
	public static function beginProfile($token, $category = 'application')
	{
		static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
	}

	/**
	 * 为分析标志着一个代码块的结束。
	 * This has to be matched with a previous call to [[beginProfile]] with the same category name.
	 * @param string $token token for the code block
	 * @param string $category the category of this log message
	 * @see beginProfile()
	 */
	public static function endProfile($token, $category = 'application')
	{
		static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category);
	}

	/**
	 * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Leaps" information.
	 * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Leaps" information
	 */
	public static function powered()
	{
		return 'Powered by <a href="http://leaps.tintsoft.com/" rel="external">Leaps Framework</a>';
	}

	/**
	 * 翻译一个消息到指定的语言。
	 *
	 * 这是一个快捷的方法 [[\Leaps\I18n\I18N::translate()]].
	 *
	 * 翻译将根据信息类别和将使用目标语言。
	 *
	 * In case when a translated message has different plural forms (separated by "|"), this method
	 * will also attempt to choose an appropriate one according to a given numeric value which is
	 * specified as the first parameter (indexed by 0) in `$params`.
	 *
	 * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first
	 * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}"
	 * will be replaced with the given number.
	 *
	 * For more details on how plural rules are applied, please refer to:
	 * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]]
	 *
	 * @param string $category the message category.
	 * @param string $message the message to be translated.
	 * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
	 * @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current
	 * [[\Leaps\Base\Application::language|application language]] will be used.
	 * @return string the translated message.
	 */
	public static function t($category, $message, $params = [], $language = null)
	{
		if (static::$app !== null) {
			return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language);
		} else {
			$p = [];
			foreach ((array) $params as $name => $value) {
				$p['{' . $name . '}'] = $value;
			}
			return ($p === []) ? $message : strtr($message, $p);
		}
	}

	/**
	 * 配置一个对象的初始属性值。
	 * @param object $object 对象配置
	 * @param array $properties 属性初始值给定的名称-值对。
	 */
	public static function configure($object, $properties)
	{
		foreach ($properties as $name => $value) {
			$object->$name = $value;
		}
	}

	/**
	 * 返回对象的公共成员变量。
	 * @param object $object 处理的对象
	 * @return array 对象的公共成员变量
	 */
	public static function getObjectVars($object)
	{
		return get_object_vars($object);
	}
}